/**
 * Dynamically create and position certain menus and sub menus (JSON objects) depending on specific conditions.
 *
 * @copyright   2010, Blizzard Entertainment, Inc
 * @class       Menu
 * @requires    Page, jQuery
 * @example
 *
 *      Menu.data = {
 *          children: [
 *              {
 *                  label: "Foo",
 *                  url:   "/foo/bar"
 *              }
 *          ]
 *      }
 *
 *      .menu { padding: 0; width: 200px; position: absolute; }
 *      .menu:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
 *      .menu ul { list-style: none; margin: 0; padding: 0; }
 *      .menu ul li { clear: both; float: left; width: 200px; position: relative; }
 *      .menu ul li a { display: block; padding: 5px; }
 *      .menu ul li a:hover { }
 *      .menu ul li .menu { left: 200px; top: -1px; display: none; }
 *      .menu .menu-header { padding: 5px; display: block; }
 */

var Menu = {

    /**
     * Base menu object data.
     */
	data: {},

    /**
     * Menu object data indexed by path.
     */
    dataIndex: {},

	/**
	 * The main container element.
	 */
	container: null,

	/**
	 * The element that opened the drop down menu.
	 */
	node: null,

	/**
	 * Element ID to prepend to cached menus.
	 */
	idName: 'menu-tier',

	/**
	 * Wrapper class name for all sub menus.
	 */
	className: 'flyout-menu',

	/**
	 * Timer to kill the menu.
	 */
	timer: null,

	/**
	 * Timer to open the menu after a duration.
	 */
	openTimer: null,

    /**
     * Misc configuration.
     */
    config: {
        duration: 750,
        dataUrl: '',
        colWidth: 200,
        colMax: 15
    },

	/**
	 * Initialize the class a store the container.
     *
     * @param string url
     * @param string base
     * @constructor
	 */
	initialize: function(url) {
		Menu.config.dataUrl = url || '';

		Menu.container = $('<div/>').attr('id', 'menu-container').appendTo('body');
		Menu.container
			.mouseleave(function() {
				Menu.timer = window.setTimeout(function() {
					Menu.hide();
				}, Menu.config.duration);
			})
			.mouseenter(function() {
				window.clearTimeout(Menu.timer);
			});

        // Get the data
        if (Menu.config.dataUrl) {
            $.ajax({
                url: Core.baseUrl + Menu.config.dataUrl,
                success: function(data) {
                    Menu.data = data;
                    Menu._populate(data);
                }
            });
        }

		// Bind the handlers
		var crumbs = $('.ui-breadcrumb li a');

		$.each(crumbs, function(key, crumb) {
			var anchor = $(crumb);
			var url = anchor.attr('href').replace(Core.baseUrl, '');

			anchor.mouseover(function() {
				Menu.show(this, url);
			});
		});
	},

	/**
	 * Show a specific menu at a specific location.
     * Used in conjunction with onmouseover.
     *
     * @param object node
     * @param string path
     * @param boolean centerMenu
	 */
	show: function(node, path, centerMenu) {
		Menu.hide();
		Menu.node = $(node);

		Menu.openTimer = window.setTimeout(function() {
			Menu._display(path, centerMenu);
		}, 150);

		Menu.node.mouseleave(function() {
			window.clearTimeout(Menu.openTimer);
		});
	},

    /**
	 * An onclick alternative to show().
     *
     * @param object node
     * @param string path
     * @param boolean centerMenu
	 */
    open: function(node, path, centerMenu) {
		Menu.node = $(node);

        var data = Menu.dataIndex[path] || null;

        if (data && data.children) {
            var targetMenu = $('#'+ Menu.idName + data.id);

            if (targetMenu.is(':visible'))
                Menu.hide();
            else
                Menu._display(path, centerMenu);
        }
    },

	/**
	 * Hide / reset the menu system.
	 */
	hide: function() {
		window.clearTimeout(Menu.timer);
		window.clearTimeout(Menu.openTimer);

		Menu.container.find('div').hide();

		if (Menu.node) {
			Menu.node.removeClass('opened');
			Menu.node = null;
		}
	},

	/**
	 * Create the div/ul elements and append it to the parent.
	 * Cycle through the links and create the li/a elements, and build children if available.
     *
     * @param object parent - Parent node to add to
     * @param object menu - Object containing children
     * @param string cache
	 */
	_build: function(parent, menu, cache) {
		var div = $('<div>').addClass(Menu.className);
        var uls = [];

        if (cache)
            div.attr('id', Menu.idName + menu.id);

        $.each(menu.children, function(key, data) {
            var tag = (data.url == null) ? 'span' : 'a';
            var li = $('<li>');
            var item = $('<'+ tag +'>', Menu._prepare(data)).appendTo(li);

            if (data.parentClass)
                li.addClass(data.parentClass);

            // Bind hovers and children
            if (data.children) {
                item.addClass('children');

                li.hover(
                    function() {
                        var that = $(this);
                        that.find('a:first').addClass('opened');

                        if (that.find('.'+ Menu.className).length == 0)
                            Menu._build(this, data, false);

                        Menu._position(that.children('.'+ Menu.className));
                    },
                    function() {
                        var that = $(this);
                        that.find('a:first').removeClass('opened');
                        that.children('.'+ Menu.className).hide();
                    }
                );
            }

            // Determine which list
            var index = Math.ceil((key + 1) / Menu.config.colMax) - 1;

            if (!uls[index])
                uls[index] = $('<ul/>');

            li.appendTo(uls[index]);
        });

        // Append
		for (var i = 0; i <= (uls.length - 1); ++i) {
			var ul = uls[i];
			$(ul).appendTo(div);
		}

        if (uls.length > 1)
			div.css('width', (uls.length * Menu.config.colWidth));

        div.appendTo(parent);
	},

	/**
	 * Position the parent menu at the location.
	 *
	 * @param boolean centerMenu
	 * @return void
	 */
	_display: function(path, centerMenu) {
		var data = Menu.dataIndex[path] || null;

        if (data && data.children) {
            var targetMenu = $('#'+ Menu.idName + data.id);

            if (targetMenu.length > 0)
                targetMenu.fadeIn('fast');
            else
                Menu._build(Menu.container, data, true);

			Menu.node.addClass('opened');

			// Position menu accordingly
			var nodeOffset = Menu.node.parent().offset();
			var nodeWidth = Menu.node.parent().width();
			var x = nodeOffset.left;
			var y = nodeOffset.top + Menu.node.outerHeight();
			var opts = {
				top: y +'px',
				position: 'absolute',
				visibility: 'visible',
				zIndex: '75'
			};

			if (centerMenu) {
				var width = Math.round(nodeWidth / 2) - Math.round(Menu.config.colWidth / 2);
				opts.left = (x + width) +'px';
			} else {
				opts.left = x +'px';
			}

			Menu.container.css(opts).show();
		}
	},

	/**
	 * Show the element, and reposition it if it goes off the page.
	 *
	 * @param object element
	 */
	_position: function(element) {
		element.show();

		if (element.find('ul').length <= 1) {
			var offset = element.offset();
			var width  = element.outerWidth();
			var x = offset.left + width;

			if (x >= Page.dimensions.width)
				element.css('left', -width);
		}
	},

    /**
     * Populate the class with data returned from the server.
     *
     * @param object node
     * @return void
     */
    _populate: function(node) {
        if (!Menu.dataIndex[node.url]) {
            node.id = node.url.replace(/\//ig, '-');

            if (node.id.substr(-1) == '-')
                node.id = node.id.substr(0, (node.id.length - 1));

            Menu.dataIndex[node.url] = node;
        }

        if (node.children) {
            for (var i = 0, child; child = node.children[i]; i++) {
                Menu._populate(child);
            }
        }
    },

	/**
	 * Prepare the element params based on a whitelist.
	 *
	 * @param object obj
	 * @return object
	 */
	_prepare: function(obj) {
		var mapping = { html: obj.label, rel: 'np' }
		var params = {};

        if (obj.url != null)
            mapping.href = Core.baseUrl + obj.url;

		$.each(mapping, function(key, value) {
			if (value)
				params[key] = value;
		});

		return params;
	}

};
